using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;
using PluginCore.DockPanelSuite;
using PluginCore.Helpers;
using PluginCore;

namespace WeifenLuo.WinFormsUI.Docking
{
    partial class DockPanel
    {
        sealed class DockDragHandler : DragHandler
        {
            class DockIndicator : DragForm
            {
                #region IHitTest

                interface IHitTest
                {
                    DockStyle HitTest(Point pt);
                    DockStyle Status { get; set; }
                }
                #endregion

                #region PanelIndicator

                class PanelIndicator : PictureBox, IHitTest
                {
                    static readonly Image _imagePanelLeft = ScaleHelper.Scale(Resources.DockIndicator_PanelLeft);
                    static readonly Image _imagePanelRight = ScaleHelper.Scale(Resources.DockIndicator_PanelRight);
                    static readonly Image _imagePanelTop = ScaleHelper.Scale(Resources.DockIndicator_PanelTop);
                    static readonly Image _imagePanelBottom = ScaleHelper.Scale(Resources.DockIndicator_PanelBottom);
                    static readonly Image _imagePanelFill = ScaleHelper.Scale(Resources.DockIndicator_PanelFill);
                    static readonly Image _imagePanelLeftActive = ScaleHelper.Scale(Resources.DockIndicator_PanelLeft_Active);
                    static readonly Image _imagePanelRightActive = ScaleHelper.Scale(Resources.DockIndicator_PanelRight_Active);
                    static readonly Image _imagePanelTopActive = ScaleHelper.Scale(Resources.DockIndicator_PanelTop_Active);
                    static readonly Image _imagePanelBottomActive = ScaleHelper.Scale(Resources.DockIndicator_PanelBottom_Active);
                    static readonly Image _imagePanelFillActive = ScaleHelper.Scale(Resources.DockIndicator_PanelFill_Active);

                    public PanelIndicator(DockStyle dockStyle)
                    {
                        DockStyle = dockStyle;
                        SizeMode = PictureBoxSizeMode.AutoSize;
                        Image = ImageInactive;
                    }

                    DockStyle DockStyle { get; }

                    DockStyle m_status;
                    public DockStyle Status
                    {
                        get => m_status;
                        set
                        {
                            if (value != DockStyle && value != DockStyle.None)
                                throw new InvalidEnumArgumentException();

                            if (m_status == value)
                                return;

                            m_status = value;
                            IsActivated = (m_status != DockStyle.None);
                        }
                    }

                    Image ImageInactive
                    {
                        get
                        {
                            return DockStyle switch
                            {
                                DockStyle.Left => _imagePanelLeft,
                                DockStyle.Right => _imagePanelRight,
                                DockStyle.Top => _imagePanelTop,
                                DockStyle.Bottom => _imagePanelBottom,
                                DockStyle.Fill => _imagePanelFill,
                                _ => null
                            };
                        }
                    }

                    Image ImageActive
                    {
                        get
                        {
                            return DockStyle switch
                            {
                                DockStyle.Left => _imagePanelLeftActive,
                                DockStyle.Right => _imagePanelRightActive,
                                DockStyle.Top => _imagePanelTopActive,
                                DockStyle.Bottom => _imagePanelBottomActive,
                                DockStyle.Fill => _imagePanelFillActive,
                                _ => null
                            };
                        }
                    }

                    bool m_isActivated;

                    bool IsActivated
                    {
                        get => m_isActivated;
                        set
                        {
                            m_isActivated = value;
                            Image = IsActivated ? ImageActive : ImageInactive;
                        }
                    }

                    public DockStyle HitTest(Point pt)
                    {
                        return Visible && ClientRectangle.Contains(PointToClient(pt)) ? DockStyle : DockStyle.None;
                    }
                }
                #endregion PanelIndicator

                #region PaneIndicator

                class PaneIndicator : PictureBox, IHitTest
                {
                    struct HotSpotIndex
                    {
                        public HotSpotIndex(int x, int y, DockStyle dockStyle)
                        {
                            X = x;
                            Y = y;
                            DockStyle = dockStyle;
                        }

                        public int X { get; }

                        public int Y { get; }

                        public DockStyle DockStyle { get; }
                    }

                    static readonly Bitmap _bitmapPaneDiamond = ScaleHelper.Scale(Resources.DockIndicator_PaneDiamond);
                    static readonly Bitmap _bitmapPaneDiamondLeft = ScaleHelper.Scale(Resources.DockIndicator_PaneDiamond_Left);
                    static readonly Bitmap _bitmapPaneDiamondRight = ScaleHelper.Scale(Resources.DockIndicator_PaneDiamond_Right);
                    static readonly Bitmap _bitmapPaneDiamondTop = ScaleHelper.Scale(Resources.DockIndicator_PaneDiamond_Top);
                    static readonly Bitmap _bitmapPaneDiamondBottom = ScaleHelper.Scale(Resources.DockIndicator_PaneDiamond_Bottom);
                    static readonly Bitmap _bitmapPaneDiamondFill = ScaleHelper.Scale(Resources.DockIndicator_PaneDiamond_Fill);
                    static readonly Bitmap _bitmapPaneDiamondHotSpot = ScaleHelper.Scale(Resources.DockIndicator_PaneDiamond_Hotspot);
                    static readonly Bitmap _bitmapPaneDiamondHotSpotIndex = Resources.DockIndicator_PaneDiamond_HotspotIndex;

                    static readonly HotSpotIndex[] _hotSpots = {
                new HotSpotIndex(1, 0, DockStyle.Top),
                new HotSpotIndex(0, 1, DockStyle.Left),
                new HotSpotIndex(1, 1, DockStyle.Fill),
                new HotSpotIndex(2, 1, DockStyle.Right),
                new HotSpotIndex(1, 2, DockStyle.Bottom)
            };

                    public PaneIndicator()
                    {
                        SizeMode = PictureBoxSizeMode.AutoSize;
                        Image = _bitmapPaneDiamond;
                        Region = new Region(DisplayingGraphicsPath);
                    }

                    public static GraphicsPath DisplayingGraphicsPath { get; } = DrawHelper.CalculateGraphicsPathFromBitmap(_bitmapPaneDiamond);

                    public DockStyle HitTest(Point pt)
                    {
                        if (!Visible)
                            return DockStyle.None;

                        pt = PointToClient(pt);
                        if (!ClientRectangle.Contains(pt))
                            return DockStyle.None;

                        for (int i = _hotSpots.GetLowerBound(0); i <= _hotSpots.GetUpperBound(0); i++)
                        {
                            if (_bitmapPaneDiamondHotSpot.GetPixel(pt.X, pt.Y) == _bitmapPaneDiamondHotSpotIndex.GetPixel(_hotSpots[i].X, _hotSpots[i].Y))
                                return _hotSpots[i].DockStyle;
                        }

                        return DockStyle.None;
                    }

                    DockStyle m_status = DockStyle.None;
                    public DockStyle Status
                    {
                        get => m_status;
                        set
                        {
                            m_status = value;
                            Image = m_status switch
                            {
                                DockStyle.None => _bitmapPaneDiamond,
                                DockStyle.Left => _bitmapPaneDiamondLeft,
                                DockStyle.Right => _bitmapPaneDiamondRight,
                                DockStyle.Top => _bitmapPaneDiamondTop,
                                DockStyle.Bottom => _bitmapPaneDiamondBottom,
                                DockStyle.Fill => _bitmapPaneDiamondFill,
                                _ => Image
                            };
                        }
                    }
                }
                #endregion PaneIndicator

                #region consts

                readonly int _PanelIndicatorMargin = 10;
                #endregion

                public DockIndicator(DockDragHandler dragHandler)
                {
                    DragHandler = dragHandler;
                    Controls.AddRange(new Control[] {
                        PaneDiamond,
                        PanelLeft,
                        PanelRight,
                        PanelTop,
                        PanelBottom,
                        PanelFill
                        });
                    Region = new Region(Rectangle.Empty);
                }

                PaneIndicator m_paneDiamond;

                PaneIndicator PaneDiamond => m_paneDiamond ??= new PaneIndicator();

                PanelIndicator m_panelLeft;

                PanelIndicator PanelLeft => m_panelLeft ??= new PanelIndicator(DockStyle.Left);

                PanelIndicator m_panelRight;

                PanelIndicator PanelRight => m_panelRight ??= new PanelIndicator(DockStyle.Right);

                PanelIndicator m_panelTop;

                PanelIndicator PanelTop => m_panelTop ??= new PanelIndicator(DockStyle.Top);

                PanelIndicator m_panelBottom;

                PanelIndicator PanelBottom => m_panelBottom ??= new PanelIndicator(DockStyle.Bottom);

                PanelIndicator m_panelFill;

                PanelIndicator PanelFill => m_panelFill ??= new PanelIndicator(DockStyle.Fill);

                bool m_fullPanelEdge;
                public bool FullPanelEdge
                {
                    get => m_fullPanelEdge;
                    set
                    {
                        if (m_fullPanelEdge == value)
                            return;

                        m_fullPanelEdge = value;
                        RefreshChanges();
                    }
                }

                public DockDragHandler DragHandler { get; }

                public DockPanel DockPanel => DragHandler.DockPanel;

                DockPane m_dockPane;
                public DockPane DockPane
                {
                    get => m_dockPane;
                    internal set
                    {
                        if (m_dockPane == value)
                            return;

                        DockPane oldDisplayingPane = DisplayingPane;
                        m_dockPane = value;
                        if (oldDisplayingPane != DisplayingPane)
                            RefreshChanges();
                    }
                }

                IHitTest m_hitTest;

                IHitTest HitTestResult
                {
                    get => m_hitTest;
                    set
                    {
                        if (m_hitTest == value)
                            return;

                        if (m_hitTest != null)
                            m_hitTest.Status = DockStyle.None;

                        m_hitTest = value;
                    }
                }

                DockPane DisplayingPane => ShouldPaneDiamondVisible() ? DockPane : null;

                void RefreshChanges()
                {
                    Region region = new Region(Rectangle.Empty);
                    Rectangle rectDockArea = FullPanelEdge ? DockPanel.DockArea : DockPanel.DocumentWindowBounds;

                    rectDockArea = RectangleToClient(DockPanel.RectangleToScreen(rectDockArea));
                    if (ShouldPanelIndicatorVisible(DockState.DockLeft))
                    {
                        PanelLeft.Location = new Point(rectDockArea.X + _PanelIndicatorMargin, rectDockArea.Y + (rectDockArea.Height - PanelRight.Height) / 2);
                        PanelLeft.Visible = true;
                        region.Union(PanelLeft.Bounds);
                    }
                    else
                        PanelLeft.Visible = false;

                    if (ShouldPanelIndicatorVisible(DockState.DockRight))
                    {
                        PanelRight.Location = new Point(rectDockArea.X + rectDockArea.Width - PanelRight.Width - _PanelIndicatorMargin, rectDockArea.Y + (rectDockArea.Height - PanelRight.Height) / 2);
                        PanelRight.Visible = true;
                        region.Union(PanelRight.Bounds);
                    }
                    else
                        PanelRight.Visible = false;

                    if (ShouldPanelIndicatorVisible(DockState.DockTop))
                    {
                        PanelTop.Location = new Point(rectDockArea.X + (rectDockArea.Width - PanelTop.Width) / 2, rectDockArea.Y + _PanelIndicatorMargin);
                        PanelTop.Visible = true;
                        region.Union(PanelTop.Bounds);
                    }
                    else
                        PanelTop.Visible = false;

                    if (ShouldPanelIndicatorVisible(DockState.DockBottom))
                    {
                        PanelBottom.Location = new Point(rectDockArea.X + (rectDockArea.Width - PanelBottom.Width) / 2, rectDockArea.Y + rectDockArea.Height - PanelBottom.Height - _PanelIndicatorMargin);
                        PanelBottom.Visible = true;
                        region.Union(PanelBottom.Bounds);
                    }
                    else
                        PanelBottom.Visible = false;

                    if (ShouldPanelIndicatorVisible(DockState.Document))
                    {
                        Rectangle rectDocumentWindow = RectangleToClient(DockPanel.RectangleToScreen(DockPanel.DocumentWindowBounds));
                        PanelFill.Location = new Point(rectDocumentWindow.X + (rectDocumentWindow.Width - PanelFill.Width) / 2, rectDocumentWindow.Y + (rectDocumentWindow.Height - PanelFill.Height) / 2);
                        PanelFill.Visible = true;
                        region.Union(PanelFill.Bounds);
                    }
                    else
                        PanelFill.Visible = false;

                    if (ShouldPaneDiamondVisible())
                    {
                        Rectangle rect = RectangleToClient(DockPane.RectangleToScreen(DockPane.ClientRectangle));
                        PaneDiamond.Location = new Point(rect.Left + (rect.Width - PaneDiamond.Width) / 2, rect.Top + (rect.Height - PaneDiamond.Height) / 2);
                        PaneDiamond.Visible = true;
                        using GraphicsPath graphicsPath = PaneIndicator.DisplayingGraphicsPath.Clone() as GraphicsPath;
                        var pts = new[]
                        {
                            new Point(PaneDiamond.Left, PaneDiamond.Top),
                            new Point(PaneDiamond.Right, PaneDiamond.Top),
                            new Point(PaneDiamond.Left, PaneDiamond.Bottom)
                        };
                        using var matrix = new Matrix(PaneDiamond.ClientRectangle, pts);
                        graphicsPath.Transform(matrix);
                        region.Union(graphicsPath);
                    }
                    else
                        PaneDiamond.Visible = false;

                    Region = region;
                }

                bool ShouldPanelIndicatorVisible(DockState dockState)
                {
                    return Visible
                           && !DockPanel.DockWindows[dockState].Visible
                           && DragHandler.DragSource.IsDockStateValid(dockState);
                }

                bool ShouldPaneDiamondVisible()
                {
                    return DockPane is not null
                           && DockPanel.AllowEndUserNestedDocking
                           && DragHandler.DragSource.CanDockTo(DockPane);
                }

                public override void Show(bool bActivate)
                {
                    base.Show(bActivate);
                    Bounds = SystemInformation.VirtualScreen;
                    RefreshChanges();
                }

                public void TestDrop()
                {
                    Point pt = MousePosition;
                    DockPane = DockHelper.PaneAtPoint(pt, DockPanel);

                    if (TestDrop(PanelLeft, pt) != DockStyle.None)
                        HitTestResult = PanelLeft;
                    else if (TestDrop(PanelRight, pt) != DockStyle.None)
                        HitTestResult = PanelRight;
                    else if (TestDrop(PanelTop, pt) != DockStyle.None)
                        HitTestResult = PanelTop;
                    else if (TestDrop(PanelBottom, pt) != DockStyle.None)
                        HitTestResult = PanelBottom;
                    else if (TestDrop(PanelFill, pt) != DockStyle.None)
                        HitTestResult = PanelFill;
                    else if (TestDrop(PaneDiamond, pt) != DockStyle.None)
                        HitTestResult = PaneDiamond;
                    else
                        HitTestResult = null;

                    if (HitTestResult != null)
                    {
                        if (HitTestResult is PaneIndicator)
                            DragHandler.Outline.Show(DockPane, HitTestResult.Status);
                        else
                            DragHandler.Outline.Show(DockPanel, HitTestResult.Status, FullPanelEdge);
                    }
                }

                static DockStyle TestDrop(IHitTest hitTest, Point pt) => hitTest.Status = hitTest.HitTest(pt);

                static Rectangle GetAllScreenBounds()
                {
                    Rectangle rect = new Rectangle(0, 0, 0, 0);
                    foreach (Screen screen in Screen.AllScreens)
                    {
                        Rectangle rectScreen = screen.Bounds;
                        if (rectScreen.Left < rect.Left)
                        {
                            rect.Width += (rect.Left - rectScreen.Left);
                            rect.X = rectScreen.X;
                        }
                        if (rectScreen.Right > rect.Right)
                            rect.Width += (rectScreen.Right - rect.Right);
                        if (rectScreen.Top < rect.Top)
                        {
                            rect.Height += (rect.Top - rectScreen.Top);
                            rect.Y = rectScreen.Y;
                        }
                        if (rectScreen.Bottom > rect.Bottom)
                            rect.Height += (rectScreen.Bottom - rect.Bottom);
                    }

                    return rect;
                }
            }

            class DockOutline : DockOutlineBase
            {
                public DockOutline()
                {
                    DragForm = new DragForm();
                    SetDragForm(Rectangle.Empty);
                    DragForm.BackColor = SystemColors.ActiveCaption;
                    DragForm.Opacity = 0.5;
                    DragForm.Show(false);
                }

                DragForm DragForm { get; }

                protected override void OnShow()
                {
                    CalculateRegion();
                }

                protected override void OnClose()
                {
                    DragForm.Close();
                }

                void CalculateRegion()
                {
                    if (SameAsOldValue)
                        return;

                    if (!FloatWindowBounds.IsEmpty)
                        SetOutline(FloatWindowBounds);
                    else if (DockTo is DockPanel)
                        SetOutline(DockTo as DockPanel, Dock, (ContentIndex != 0));
                    else if (DockTo is DockPane)
                        SetOutline(DockTo as DockPane, Dock, ContentIndex);
                    else
                        SetOutline();
                }

                void SetOutline()
                {
                    SetDragForm(Rectangle.Empty);
                }

                void SetOutline(Rectangle floatWindowBounds)
                {
                    SetDragForm(floatWindowBounds);
                }

                void SetOutline(DockPanel dockPanel, DockStyle dock, bool fullPanelEdge)
                {
                    Rectangle rect = fullPanelEdge ? dockPanel.DockArea : dockPanel.DocumentWindowBounds;
                    rect.Location = dockPanel.PointToScreen(rect.Location);
                    if (dock == DockStyle.Top)
                    {
                        int height = dockPanel.GetDockWindowSize(DockState.DockTop);
                        rect = new Rectangle(rect.X, rect.Y, rect.Width, height);
                    }
                    else if (dock == DockStyle.Bottom)
                    {
                        int height = dockPanel.GetDockWindowSize(DockState.DockBottom);
                        rect = new Rectangle(rect.X, rect.Bottom - height, rect.Width, height);
                    }
                    else if (dock == DockStyle.Left)
                    {
                        int width = dockPanel.GetDockWindowSize(DockState.DockLeft);
                        rect = new Rectangle(rect.X, rect.Y, width, rect.Height);
                    }
                    else if (dock == DockStyle.Right)
                    {
                        int width = dockPanel.GetDockWindowSize(DockState.DockRight);
                        rect = new Rectangle(rect.Right - width, rect.Y, width, rect.Height);
                    }
                    else if (dock == DockStyle.Fill)
                    {
                        rect = dockPanel.DocumentWindowBounds;
                        rect.Location = dockPanel.PointToScreen(rect.Location);
                    }

                    SetDragForm(rect);
                }

                void SetOutline(DockPane pane, DockStyle dock, int contentIndex)
                {
                    if (dock != DockStyle.Fill)
                    {
                        Rectangle rect = pane.DisplayingRectangle;
                        if (dock == DockStyle.Right)
                            rect.X += rect.Width / 2;
                        if (dock == DockStyle.Bottom)
                            rect.Y += rect.Height / 2;
                        if (dock == DockStyle.Left || dock == DockStyle.Right)
                            rect.Width -= rect.Width / 2;
                        if (dock == DockStyle.Top || dock == DockStyle.Bottom)
                            rect.Height -= rect.Height / 2;
                        rect.Location = pane.PointToScreen(rect.Location);

                        SetDragForm(rect);
                    }
                    else if (contentIndex == -1)
                    {
                        Rectangle rect = pane.DisplayingRectangle;
                        rect.Location = pane.PointToScreen(rect.Location);
                        SetDragForm(rect);
                    }
                    else
                    {
                        using var path = pane.TabStripControl.GetOutline(contentIndex);
                        var rectF = path.GetBounds();
                        var rect = new Rectangle((int)rectF.X, (int)rectF.Y, (int)rectF.Width, (int)rectF.Height);
                        using var matrix = new Matrix(rect, new[] { new Point(0, 0), new Point(rect.Width, 0), new Point(0, rect.Height) });
                        path.Transform(matrix);
                        var region = new Region(path);
                        SetDragForm(rect, region);
                    }
                }

                void SetDragForm(Rectangle rect)
                {
                    DragForm.Bounds = rect;
                    if (rect == Rectangle.Empty)
                        DragForm.Region = new Region(Rectangle.Empty);
                    else if (DragForm.Region != null)
                        DragForm.Region = null;
                }

                void SetDragForm(Rectangle rect, Region region)
                {
                    DragForm.Bounds = rect;
                    DragForm.Region = region;
                }
            }

            public DockDragHandler(DockPanel panel)
                : base(panel)
            {
            }

            public new IDockDragSource DragSource
            {
                get => base.DragSource as IDockDragSource;
                set => base.DragSource = value;
            }

            public DockOutlineBase Outline { get; private set; }

            DockIndicator Indicator { get; set; }

            Rectangle FloatOutlineBounds { get; set; }

            public void BeginDrag(IDockDragSource dragSource)
            {
                DragSource = dragSource;

                if (!BeginDrag())
                {
                    DragSource = null;
                    return;
                }

                Outline = new DockOutline();
                Indicator = new DockIndicator(this);
                Indicator.Show(false);

                FloatOutlineBounds = DragSource.BeginDrag(StartMousePosition);
            }

            protected override void OnDragging()
            {
                TestDrop();
            }

            protected override void OnEndDrag(bool abort)
            {
                DockPanel.SuspendLayout(true);

                Outline.Close();
                Indicator.Close();

                EndDrag(abort);

                DockPanel.ResumeLayout(true, true);

                DragSource = null;
            }

            void TestDrop()
            {
                Outline.FlagTestDrop = false;

                Indicator.FullPanelEdge = ((ModifierKeys & Keys.Shift) != 0);

                if ((ModifierKeys & Keys.Control) == 0)
                {
                    Indicator.TestDrop();

                    if (!Outline.FlagTestDrop)
                    {
                        DockPane pane = DockHelper.PaneAtPoint(MousePosition, DockPanel);
                        if (pane != null && DragSource.IsDockStateValid(pane.DockState))
                            pane.TestDrop(DragSource, Outline);
                    }

                    if (!Outline.FlagTestDrop && DragSource.IsDockStateValid(DockState.Float))
                    {
                        FloatWindow floatWindow = DockHelper.FloatWindowAtPoint(MousePosition, DockPanel);
                        floatWindow?.TestDrop(DragSource, Outline);
                    }
                }
                else
                    Indicator.DockPane = DockHelper.PaneAtPoint(MousePosition, DockPanel);

                if (!Outline.FlagTestDrop)
                {
                    if (DragSource.IsDockStateValid(DockState.Float))
                    {
                        Rectangle rect = FloatOutlineBounds;
                        rect.Offset(MousePosition.X - StartMousePosition.X, MousePosition.Y - StartMousePosition.Y);
                        Outline.Show(rect);
                    }
                }

                if (!Outline.FlagTestDrop)
                {
                    Cursor.Current = Cursors.No;
                    Outline.Show();
                }
                else
                    Cursor.Current = DragControl.Cursor;
            }

            void EndDrag(bool abort)
            {
                if (abort)
                    return;

                if (!Outline.FloatWindowBounds.IsEmpty)
                    DragSource.FloatAt(Outline.FloatWindowBounds);
                else switch (Outline.DockTo)
                {
                    case DockPane pane:
                        DragSource.DockTo(pane, Outline.Dock, Outline.ContentIndex);
                        break;
                    case DockPanel panel:
                        panel.UpdateDockWindowZOrder(Outline.Dock, Outline.FlagFullEdge);
                        DragSource.DockTo(panel, Outline.Dock);
                        break;
                }
                if ((DragSource as DockContentHandler)?.Content is ITabbedDocument)
                {
                    ((DockContentHandler)DragSource).Content.DockHandler.Activate();
                }
            }
        }

        DockDragHandler m_dockDragHandler;

        DockDragHandler GetDockDragHandler() => m_dockDragHandler ??= new DockDragHandler(this);

        internal void BeginDrag(IDockDragSource dragSource) => GetDockDragHandler().BeginDrag(dragSource);
    }
}
